/*	processorID.cpp
 *
 *	This class tries to gather as much information about the processor(s) in
 *	the system as it can. Currently it only uses the 'cpuid' ASM function.
 *
 *	It is largely based on ...[ToDo: insert here]
 *
 *	However, since most samples of this are a bit outdated, no strings are
 *	available for the newer cpus from both Intel and AMD. I'm hoping that by
 *	making this library available, and easy to use, people will report back
 *	to me so I can update the data in the strings.
 *
 *	I realize the way the strings are used may waste some space, but since I'm
 *	not really that bothered by space, and this makes for easy updating I've
 *	decided to do it this way. A possibility would be to enable the users of
 *	the lib to load in the files at runtime using their own system. Then the
 *	data files could be easily updated, and also stored in a zip file along
 *	with the rest of the data.
 *
 *	Platform Indepedent:		Probably, since it only uses ASM and ISO-C.
 *								Not tested yet though.
 *
 *
 *	Created:
 *			R. Appleton			14 August 2005
 */
#include <iostream>			// Only needed for the logging
#include <sstream>
#include "processorID.hpp"

//	==========================================================================
//	Defines for logging
//	==========================================================================
// Uncomment the following to disable logging.
//#define NO_LOGGING

#ifdef NO_LOGGING
	#define LOG(a) 
#else
	#define LOG(a) a
#endif



//	==========================================================================
//	CheckBit
//
//	Checks if bit 'bit' of 'var' is set.
//	==========================================================================
#define CheckBit(var, bit)   ((var & (1 << bit)) ? true : false)

//	==========================================================================
//	Define the data arrays
//	==========================================================================
std::string vendorID[] = {
	"GenuineIntel",
	"AuthenticAMD",
};

// From 'Intel Processor Identification and the CPUID Instruction' page 20
std::string Intel_ProcessorType[] = {
	"Original OEM",
	"Overdrive",
	"Dual",
	"Reserved",
};
std::string familyData[2][16] =
{
	#include "familydata.txt"
};
std::string extendedFamilyData[2][16] =
{
	#include "extendedfamilydata.txt"
};
std::string models[2][16][16] =
{
	{
		#include "intelmodels.txt"
	} ,
	{
		#include "amdmodels.txt"
	}
};

std::string brandID[2][16] = 
{
	#include "brandID.txt"
};


//	==========================================================================
//	bool ProcessorID::CheckCPUIDPresence()
// 
//	Checks if the CPUID command is available on the current processor
//
//	Out:
//			true		processor supports cpuid
//			false		processor does not support cpuid, or some other error.
//	==========================================================================
bool ProcessorID::CheckCPUIDPresence()
{
	unsigned long BitChanged;

	// We have to check if we can toggle the flag register bit 21
	// If we can't the processor does not support the CPUID command
	LOG(std::cout << "Checking for presence of cpuid....." << std::flush);
	__asm
	{
		pushfd
		pop eax
		mov ebx, eax
		xor eax, 0x00200000 
		push eax
		popfd
		pushfd
		pop eax
		xor eax,ebx 
		mov BitChanged, eax
	}
	LOG(std::cout << "OK" << std::endl);

	return ((BitChanged) ? true : false);
}

//	==========================================================================
//	void ProcessorID::GetAllData()
// 
//	Get as much data as we can from CPUID. After this we have all the data,
//	so 'cpuid' need not be called again.
//	==========================================================================
void ProcessorID::GetAllData( void )
{
	// Clear the data arrays in case we've already run before
	m_Data.clear();
	m_DataExtended.clear();

	// Temporary data
	regData d;

	// Get basic data
	unsigned long start = 0x00000000, end = 0x80000000;
	while(start<=end)
	{
		// Call 'cpuid'
		LOG(std::cout << "Reading data " << start << " from cpuid ....." << std::flush);
		__asm {
			mov	eax, start
			cpuid
			mov	d.eax, eax
			mov	d.ebx, ebx
			mov	d.ecx, ecx
			mov	d.edx, edx
		}
		LOG(std::cout << "OK" << std::endl);

		// If it's the first, then the last value is in d.eax, store it
		if(start==0x00000000)
			end = d.eax;

		// Store in our vector
		m_Data.push_back(d);

		// Next
		++start;
	}

	// And get extended data
	start = 0x80000000, end = 0x80000000;
	while(start<=end)
	{
		LOG(std::cout << "Reading data 0x8000000" << (start-0x80000000) << " from cpuid ....." << std::flush);
		// Call 'cpuid'
		__asm {
			mov	eax, start
			cpuid
			mov	d.eax, eax
			mov	d.ebx, ebx
			mov	d.ecx, ecx
			mov	d.edx, edx
		}
		LOG(std::cout << "OK" << std::endl);

		// If it's the first, then the last value is in d.eax, store it
		if(start==0x80000000)
			end = d.eax;

		// Store in our vector
		m_DataExtended.push_back(d);

		// Next
		++start;
	}
}

//	==========================================================================
//	void ProcessorID::SetVendorID()
// 
//	Sets the vendorID
//	==========================================================================
void ProcessorID::SetVendorID( void )
{
	// Is this data present?
	if(m_Data.size()>0)
	{
		switch(m_Data[0].ebx)
		{
		case VENDOR_GENUINEINTEL:		m_VendorID = INTEL;	break;
		case VENDOR_AUTHENTICAMD:		m_VendorID = AMD;	break;
		}
	}
}

//	==========================================================================
//	void ProcessorID::SetProcType()
// 
//	Sets data about the stepping, model and family and type
//	==========================================================================
void ProcessorID::SetProcType( void )
{
	if(m_VendorID==INTEL && m_Data.size()==2)
	{	// From 'Intel Processor Identification and the CPUID Instruction' page 21
		// This is presumably an older Intel processor.
		// ToDo: need to check this. It says in EDX, but resulting from what EAX call? 0 or 1?
		// From 'amdprocrecog.pdf' page 8
		regData& data		= m_Data.at(1);
		m_Revision			= static_cast<uint8>((data.eax) & 0xF);
		m_Model				= static_cast<uint8>((data.eax>>4) & 0xF);
		m_Family			= static_cast<uint8>((data.eax>>8) & 0xF);
		m_Type				= static_cast<uint8>((data.eax>>12) & 0xF);
	}

	// Is this data present?
	if(m_Data.size()>1)
	{
		regData& data		= m_Data.at(1);

		// From 'amdprocrecog.pdf' page 8
		m_Revision			= static_cast<uint8>((data.eax) & 0xF);
		m_Model				= static_cast<uint8>((data.eax>>3) & 0xF);
		m_Family			= static_cast<uint8>((data.eax>>8) & 0xF);

		// From 'Intel Processor Identification and the CPUID Instruction' page 16
		m_Type				= static_cast<uint8>((data.eax>>12) & 0x4);
		m_ExtendedModel		= static_cast<uint8>((data.eax>>16) & 0xF);
		m_ExtendedFamily	= static_cast<uint8>((data.eax>>20) & 0xFF);
		// See 'Intel Processor Identification and the CPUID Instruction' page 36 for BrandID descriptions
		m_Brand				= static_cast<uint8>(data.ebx & 0xFF);
	}

	// Is this data present?
	if(m_DataExtended.size()>3)
	{
		// This is where the name starts, cast directly into the string
		m_VendorCPUName = (char*)(&m_DataExtended.at(2).eax);

		// Apparently Intels have a lot of spaces first, so remove leading spaces
		int i=0;
		while(m_VendorCPUName[i]==' ')
			++i;
		m_VendorCPUName = m_VendorCPUName.substr(i);
	}

	if(m_VendorID==INTEL)
		SetProcTypeIntel();
	else if(m_VendorID==AMD)
		SetProcTypeAMD();
}

//	==========================================================================
//	void ProcessorID::SetProcTypeAMD()
// 
//	Sets data about the stepping, model and family and type of an AMD cpu.
//	==========================================================================
// From 'AMD Processor Recognition Application Note' page 44
char amdstring[64][30] = 
{
	"AMD Engineering Sample %d",
	"-",
	"-",
	"-",
	"AMD Athlon 64 %s00+",	//UP Client add22
	"-",
	"-",
	"-",
	"Mobile AMD Athlon 64 %s00+",	//Mobile client add22
	"Mobile AMD Athlon 64 %s00+",	//Mobile client, Low-Power Mobile VIDs add22
	"-",
	"-",
	"AMD Opteron 1%s",	//UP Server	=38+2*NN
	"-"
	"AMD Opteron 1%s HE",	//UP Server Low-Power 55W =38+2*NN
	"AMD Opteron 1%s EE",	//UP Server Low-Power 30W =38+2*NN

	//16
	"AMD Opteron 2%s",	//2P Server =38+2*NN
	"-",
	"AMD Opteron 2%s HE",	//2P Server Low-Power 55W =38+2*NN
	"AMD Opteron 2%s EE",	//2P Server Low-Power 30W =38+2*NN
	"AMD Opteron 8%s EE",	//MP Server =38+2*NN
	"-",
	"AMD Opteron 8%s HE",	//MP Server Low-Power 55W =38+2*NN
	"AMD Opteron 8%s EE",	//MP Server Low-Power 30W =38+2*NN
	"-",
	"-",
	"-",
	"-",
	"-",
	"Mobile AMD Athlon XP-M %s00+",		//Mobile client, 32-bit add22
	"Mobile AMD Athlon XP-M %s00+",		//Mobile client, 32-bit Low-Power Mobile VIDs add22
	"-"
	"AMD Athlon XP %s00+",				//Desktop client, 32-bit add22
	"Mobile AMD Sempron %s00+",			//Mobile client, 32-bit add24
	"AMD Sempron %s00+",				//Desktop client, 32-bit add24
	"Mobile AMD Sempron %s00+",			//Mobile client, 32-bit Low-Power Mobile VIDs add24
	"AMD Athlon FX-%s",					//Desktop client add24
};

void ProcessorID::SetProcTypeAMD( void )
{
	// Get the correct BrandID
	unsigned int uBrandID8, uBrandID12;
	if(m_Data.size()>1)
		uBrandID8 = (m_Data.at(1).ebx & 0xFF);
	// According to page 23 it's 'CPUID extended function 0' ==> 8000_0000
	// According to page 43 it's extended function 1 ==> 8000_0001
	if(m_DataExtended.size()>1)
		uBrandID12 = (m_Data.at(1).ebx & 0xFFF);

	//std::cout << "AMDBrands 8:" << uBrandID8 << " 12:" << uBrandID12 << std::endl;
	if(uBrandID8!=0)
	{
		unsigned int uIndex = (uBrandID8 >> 5) & 0x7;  // ??? three bits
		m_Model				= (uBrandID8 & 0x1F);
	}
	else if(uBrandID12!=0)
	{
		unsigned int uIndex = (uBrandID12 >> 7) & 0x7F;  // ??? six bits
		m_Model				= (uBrandID12 & 0x7F);
		std::cout << "AMDString: " << amdstring[uIndex] << std::endl;
	}
	else
	{	// From 'AMD Processor Recognition Application Note' page 23
		// This is an engineering sample
	}
	m_Brand = uBrandID8 ? uBrandID8 : uBrandID12;
}

std::string intelDescriptors[] =
{
#include "inteldescriptors.txt"
};

//	==========================================================================
//	void ProcessorID::SetProcTypeIntel()
// 
//	Sets data about the stepping, model and family and type of an Intel cpu.
//	==========================================================================
void ProcessorID::SetProcTypeIntel( void )
{
	if(m_Data.size()>2)
	{
		// From 'Intel Processor Identification and the CPUID Instruction' page 28
		if(!CheckBit(m_Data.at(2).eax, 31))
		{
			int nofTimesToExecute = m_Data.at(2).eax & 0xFF;
			LOG(std::cout << "Nof Times to execute cpuid for cache data: " << nofTimesToExecute << std::endl;)

			for(int cpuidCount=0; cpuidCount<nofTimesToExecute; ++ cpuidCount)
			{	// For each cpuid call
				// GetDescriptors
				int id;
				unsigned long regData;
				for(int reg=0; reg<4; ++reg)
				{	// For each register
					regData = *((unsigned long*)((char*)&m_Data.at(2+cpuidCount)+reg*sizeof(unsigned long)));
					if(!CheckBit(regData, 31))
					{	// Yep, this register contains valid descriptors
						for(int i=24; i>=0; i-=8)
						{
							if(cpuidCount==0 && reg==0 && i==0)
							{
								// Of the first cpuid call, the eax register contains in the lowest 8 bits
								// the number of time to call cpuid, so don't parse.
								;
							}
							else
							{
								id = (regData >> i) & 0xFF;
								if(id)
								{	// If id!=0, check the string table
									LOG(printf("0x%x %s\n", id, intelDescriptors[id].c_str());)
								}
							}
						}
					}
				}
			}
		}
	}
}

//	==========================================================================
//	void ProcessorID::SetCapabilities()
// 
//	Copy the capabilities of the chip from the regdata to the capabilities
//	vector.
//	==========================================================================
void ProcessorID::SetCapabilities( void )
{
	m_Capabilities.clear();

	// Is this data present?
	if(m_Data.size()>1)
	{
		unsigned long edx = m_Data.at(1).edx;
		for(int i=0; i<32; ++i)
			m_Capabilities.push_back(CheckBit(edx, i));
	}
}

//	==========================================================================
//	void ProcessorID::SetCacheInformation()
// 
//	Set some information on the caches.
//
//	Still in progress.
//	==========================================================================
void ProcessorID::SetCacheInformation( void )
{
	// Is this data present?
	if(m_DataExtended.size()>5)
	{
		unsigned long _2_4MBNofDataEntries = (m_DataExtended.at(5).eax>>16) & 0xFF;
		unsigned long _2_4MBNofInstrEntries = (m_DataExtended.at(5).eax>>0) & 0xFF;
		unsigned long _4KBNofDataEntries = (m_DataExtended.at(5).ebx>>16) & 0xFF;
		unsigned long _4KBNofInstrEntries = (m_DataExtended.at(5).ebx>>0) & 0xFF;
		m_L1CacheSizeKB = (m_DataExtended.at(5).ecx>>24);
		unsigned long L1InstrCache = (m_DataExtended.at(5).edx>>24);

		/*std::cout << _2_4MBNofDataEntries << "\t"
		<< _2_4MBNofInstrEntries << "\t"
		<< _4KBNofDataEntries << "\t"
		<< _4KBNofInstrEntries << "\t" 
		<< L1DataCache << "\t"
		<< L1InstrCache << std::endl;*/
	}

	if(m_DataExtended.size()>6)
	{
		m_L2CacheSizeKB = (m_DataExtended.at(6).ecx)>>16;
	}

}

//	==========================================================================
//	bool ProcessorID::GetInfo()
// 
//	Simply calls all the other functions to do the work.
//
//	Out:
//			true		Succesfully gathered all the data.
//			false		Processor does not support 'cpuid', or some other
//						error.
//	==========================================================================
bool ProcessorID::GetInfo( void )
{
	if(CheckCPUIDPresence())
	{
		LOG(std::cout << "Calling GetAllData" << std::endl);
		GetAllData();

		LOG(std::cout << "Calling SetVendorID" << std::endl);
		SetVendorID();
		LOG(std::cout << "Calling SetProcType" << std::endl);
		SetProcType();
		LOG(std::cout << "Calling SetCapabilities" << std::endl);
		SetCapabilities();

		// Still developing below this

		LOG(std::cout << "Calling SetCacheInformation" << std::endl);
		SetCacheInformation();
		LOG(std::cout << "Got all info" << std::endl);

		return true;
	}
	else
		return false;
}

//	==========================================================================
//	std::string ProcessorID::Print()
// 
//	Formats the data how we want it. For easy use.
//
//	Out:
//			std::string			Contains the data in a legible format.
//	==========================================================================
std::string ProcessorID::Print( void )
{
	std::ostringstream out("");
	
	// Because we index directly into arrays of data make sure the values aren't too high.
	if( m_Type<4 &&
		m_VendorID<2 &&
		m_Family<16 &&
		m_ExtendedFamily<16 &&
		m_Model<16 )
	{
		out << "\nVendor:                   " << vendorID[m_VendorID];
		out << "\nVendorCPUName:            " << m_VendorCPUName;
		out << "\nFamilytmp:                   ";
		if(m_Family<15)
			out << familyData[m_VendorID][m_Family];
		else
			out << extendedFamilyData[m_VendorID][m_ExtendedFamily];
		out << "\nType:                     " << Intel_ProcessorType[m_Type];
		
		out << "\nBrand ID:                 " << brandID[m_VendorID][m_Brand];
		out << "\nModel:                    " << models[m_VendorID][m_Family][m_Model];
		out << "\n";
		out << "\nL1 Cache size:            " << m_L1CacheSizeKB << " KB";
		out << "\nL2 Cache size:            " << m_L2CacheSizeKB << " KB";
	}

	return out.str();
}

//	==========================================================================
//	std::string ProcessorID::PrintData()
// 
//	Formats the data how we want it. This function prints it
//	exactly how it's read from 'cpuid', no conversions to strings are made.
//
//	Out:
//			std::string			Contains the data in a legible format.
//	==========================================================================
std::string ProcessorID::PrintData( void )
{
	std::ostringstream out("");
	out << "\nType:                     " << (int)m_Type;
	out << "\nFamily:                   " << (int)m_Family;
	out << "\nModel:                    " << (int)m_Model;
	out << "\nRevision:                 " << (int)m_Revision;

	// For now do this here, but need to move to more logical place
	// Check for Intel-specific hyperthreading stuff.
	if(m_VendorID==INTEL)
	{
		//SetProcTypeIntel();

		out << "\n\nIntel specific:";
		out << "\nHyper-Threading present:  " << (CheckBit(m_Data.at(1).edx, 28) ? "Yes" : "No");
		if(m_Data.size()>4)
		{
			long num = (m_Data.at(4).eax >> 26);
			out << "\nNof physical cores:       " << (num+1);
			long numLog = (m_Data.at(1).ebx >> 16) & 0xFF;
			out << "\nNof logical cores:        " << numLog;
		}
	}
	else if(m_VendorID==AMD)
	{
		out << "\n\nAMD specific:";
		bool dualcore = false;
		if(m_Data.size()>1)
		{
			// From 'dwamd_kernel_summit_08_RB.pdf' sheet 8
			// This returns true on 'all cores in a dual core package'
			dualcore = CheckBit(m_Data.at(1).edx, 28);
			out << "\nDualcore:                 " << ((dualcore) ? "Yes" : "No");
			if(dualcore)
			{
				unsigned int logical_number_of_processors = (m_Data.at(1).ebx >> 16) & 0xFF;
				out << "\nNof logical cores:        " << logical_number_of_processors;
			}
		}

		if(dualcore)
		{
			if(m_DataExtended.size()>8)
			{	// From 'dwamd_kernel_summit_08_RB.pdf' sheet 7
			// 0x8000_0008 exists
			unsigned int nofCoresInSocket = m_DataExtended.at(8).eax;
			out << "\nNof Cores in socket:      " << nofCoresInSocket;
			}
		}

		// 'dwamd_kernel_summit_08_RB.pdf' sheet 8 mentions a HTVALID bit, but which one is that?
	}


	/*RA Not at the moment
	// Print the capabilities of this cpu
	for(int i=0; i<32; ++i)
		out << "\ncap " << i << ":    " << m_Capabilities[i];//CheckBit(m_Data[1].edx, i);
	*/

	return out.str();
}


//------------------------------------------------------------------------
// Copyright (c) 2005 Rick Appleton
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would
//    be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
//    be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
//    distribution.
//
// Rick Appleton
// www.daedalus-development.net
//------------------------------------------------------------------------
